#include "rewine/Loader.hpp"

#include "dllso/DllsoInit.hpp"
#include "misc/fs_ext.hpp"
#include "misc/helper.hpp"
#include "misc/string_ext.hpp"
#include "rewine/LoggerSingleton.hpp"
#include "rewine/impl/CCustomImage.hpp"
#include "rewine/impl/CDllsoImage.hpp"
#include "rewine/impl/CDsoImage.hpp"
#include "rewine/impl/CPeImage.hpp"

#include <fcntl.h>
#include <zconf.h>

using std::string;
using std::shared_ptr;
using std::unique_ptr;

namespace rewine
{

static bool
is_name_matched(const string& filename, const string& exportName, const shared_ptr<IImage>& pImage) {
    // case sensitive
    if (!filename.empty() && filename == pImage->fullname()) {
        return true;
    }
    if (!exportName.empty()) {
        // case insensitive
        if (0 == compare_string_ignore_case(exportName, pImage->exportName()))
        {
            return true;
        }
    }
    return false;
}

CCustomImage *
Loader::createCustomImage(const std::string &exportName) {
    m_LastError = LoaderErrorCode::NONE;
    auto pImage = new CCustomImage(exportName);
    return pImage;
}

template <class T>
T *
Loader::findExistedByFullname(std::string& fullname) {
    IImage *pImage;
    auto pLibEntry = m_LibManager.getByFullname(fullname, &pImage);
    if (!pLibEntry) {
        return nullptr;
    }
    return dynamic_cast<T *>(pImage);
}

template <class T>
T *
Loader::resolveFilenameAndFindExisted(const std::string& filename, std::string& fullname) {
    fullname = m_PathResolver.resolve(filename);
    if (fullname.empty()) {
        m_LastError = LoaderErrorCode::FILE_NOT_FOUND;
        return nullptr;
    }
    return findExistedByFullname<T>(fullname);
}

CDllsoImage *
Loader::loadDllsoImage(const std::string &filename) {
    m_LastError = LoaderErrorCode::NONE;

    std::string fullname;
    auto pImage = resolveFilenameAndFindExisted<CDllsoImage>(filename, fullname);
    if (pImage) {
        return pImage;
    }
    if (fullname.empty()) {
        return nullptr;
    }

    int ret;
    ret = dllso::wine_init();
    if (ret) {
        g_Logger->error(__FLF__, "wine_init: ret={}", ret);
        return nullptr;
    }

    pImage = new CDllsoImage();
    Shelter shlImage;
    shlImage.add([pImage]() {
        delete pImage;
    });

    ret = pImage->init(fullname);
    if (ret) {
        g_Logger->error(__FLF__, "init: {} ret={}", fullname, ret);
        return nullptr;
    }

    ret = pImage->preload();
    if (ret) {
        g_Logger->error(__FLF__, "preload: {} ret={}", fullname, ret);
        return nullptr;
    }

    m_LibManager.get(pImage->exportName())->addAfter(nullptr, pImage);
    shlImage.toggle(false);

    ret = pImage->load();
    if (ret) {
        g_Logger->error(__FLF__, "load: {} ret={}", fullname, ret);
        return nullptr;
    }

    return pImage;
}

CDsoImage *
Loader::loadDsoImage(const std::string &exportName, const std::string &filename) {
    m_LastError = LoaderErrorCode::NONE;

    std::string fullname;
    auto pImage = resolveFilenameAndFindExisted<CDsoImage>(filename, fullname);
    if (pImage) {
        return pImage;
    }
    if (fullname.empty()) {
        return nullptr;
    }

    int ret;
    pImage = new CDsoImage();
    Shelter shlImage;
    shlImage.add([pImage]() {
        delete pImage;
    });

    ret = pImage->init(exportName, fullname);
    if (ret < 0) {
        g_Logger->error(__FLF__, "init: {} ret={}", fullname, ret);
        return nullptr;
    }

    m_LibManager.get(pImage->exportName())->addAfter(nullptr, pImage);
    shlImage.toggle(false);

    ret = pImage->load();
    if (ret) {
        g_Logger->error(__FLF__, "load: {} ret={}", fullname, ret);
        return nullptr;
    }

    return pImage;
}

static std::string
resolve_fullname_from_fd(int fd) {
    char buf0[FILENAME_MAX];
    size_t szUsed0 = sprintf(buf0, "/proc/self/fd/%d", fd);
    char buf1[FILENAME_MAX];
    size_t szUsed1 = readlink(buf0, buf1, FILENAME_MAX);
    return std::string(buf1, szUsed1);
}

int
Loader::loadPeImage(CPeImage *pImage, Shelter &shlImage) {
    int ret;
    ret = pImage->parse();
    if (ret) {
        g_Logger->error(__FLF__, "parse: {} ret={}", pImage->fullname(), ret);
        return -1;
    }

    m_LibManager.get(pImage->exportName())->addAfter(nullptr, pImage);
    shlImage.toggle(false);

    ret = pImage->load();
    if (ret) {
        g_Logger->error(__FLF__, "load: {} ret={}", pImage->fullname(), ret);
        return -2;
    }

    return 0;
}

CPeImage *
Loader::loadPeImage(const std::string& fullname, int fd) {
    int ret;
    auto pImage = new CPeImage();
    Shelter shlImage;
    shlImage.add([pImage]() {
        delete pImage;
    });

    pImage->setFullName(fullname);
    if (fd < 0) {
        fd = open_file_with_readonly(fullname);
    }
    ret = pImage->initFromFd(fd);
    if (ret < 0) {
        g_Logger->error(__FLF__, "initFromFd: {} ret={}", fullname, ret);
        return nullptr;
    }

    if (loadPeImage(pImage, shlImage)) {
        return nullptr;
    }
    return pImage;
}

IImage *
Loader::loadImageBySuffix(const std::string &filename) {
    m_LastError = LoaderErrorCode::NONE;

    std::string fullname;
    auto pImage = resolveFilenameAndFindExisted<CImage>(filename, fullname);
    if (pImage) {
        g_Logger->trace(__FLF__, "load existed: {}", fullname);
        return pImage;
    }
    if (fullname.empty()) {
        return nullptr;
    }

    if (ends_with_ignore_case(fullname, ".dll")) {
        g_Logger->debug(__FLF__, "open as pe: {}", filename);
        return loadPeImage(fullname, -1);
    } else if (ends_with_ignore_case(fullname, ".dll.so")) {
        g_Logger->debug(__FLF__, "open as dllso: {}", filename);
        return loadDllsoImage(fullname);
    } else if (ends_with_ignore_case(fullname, ".so")) {
        g_Logger->debug(__FLF__, "open as dso: {}", filename);
        string exportName = get_basename(filename) + ".dll";
        return loadDsoImage(exportName, fullname);
    } else {
        m_LastError = LoaderErrorCode::UNKNOWN_FORMAT;
        g_Logger->error(__FLF__, "open failed: unknown: {}", filename);
        return nullptr;
    }
}

IImage *
Loader::loadImageByFd(int fd)
{
    m_LastError = LoaderErrorCode::NONE;
    std::string fullname = resolve_fullname_from_fd(fd);
    if (fullname.empty()) {
        return nullptr;
    }

    if (ends_with_ignore_case(fullname, ".dll")) {
        g_Logger->debug(__FLF__, "open as pe: {}", fullname);
        return loadPeImage(fullname, fd);
    } else {
        return loadImageBySuffix(fullname);
    }
}

IImage *
Loader::openImage(const std::string &filename, bool bPin) {
    auto pImage = loadImageBySuffix(filename);
    if (!pImage) {
        return nullptr;
    }
    if (bPin) {
        pImage->setPinned(true);
    }
    return pImage;
}

IImage *
Loader::openImage(int fd, bool bPin) {
    auto pImage = loadImageByFd(fd);
    if (!pImage) {
        return nullptr;
    }
    if (bPin) {
        pImage->setPinned(true);
    }
    return pImage;
}

int
Loader::closeImage(IImage *pImage) {
    pImage->setPinned(false);
    m_LibManager.tryToClean(pImage->exportName());
    return 0;
}

} // namespace rewine
